"""
@File         : service.py
@Time         : 2024/05/06 15:49:10
@Author       : Qrj
@Email        : 923207736@qq.com
@Description  : 通知公告相关业务逻辑
"""

from datetime import datetime

from cashews import NOT_NONE
from sqlalchemy import and_, delete, func, insert, select, update
from sqlalchemy.ext.asyncio import AsyncSession

from src.exceptions.custom_async_validator import generate_async_validator_exception
from src.schemas.validate import (
    OrderBySearchValidateSchema,
    PaginationSearchValidateSchema,
)
from src.utils.cashews import CashewsCondition, data_cache

from .enums import NoticeTypeEnum
from .models import Notice
from .schemas.out import (
    ListNoticeCompleteOutSchema,
    ListNoticeNoContentOutSchema,
    NoticeCompleteOutSchema,
    NoticeNoContentOutSchema,
)
from .schemas.validate import (
    NoticeCreateValidateSchema,
    NoticeDeleteValidateSchema,
    NoticeListSearchValidateSchema,
    NoticeUpdateStatusValidateSchema,
    NoticeUpdateValidateSchema,
)


class NoticeService:
    """通知公告业务逻辑类"""

    @data_cache(
        ttl="30m",
        key="notice#id:{tenant_id}:{notice_id}",
        tags=["notice#id:{tenant_id}", "notice#id:{tenant_id}:{notice_id}"],
        condition=NOT_NONE,
    )
    @staticmethod
    async def get_notice_use_id(
        db_session: AsyncSession,
        tenant_id: int,
        notice_id: int,
        raise_not_exists_exception: bool = True,
        raise_disabled_exception: bool = True,
        raise_expired_exception: bool = True,
    ) -> NoticeCompleteOutSchema | None:
        """通过主键获取通知公告信息实例

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id (int): 租户ID
            notice_id (int): 通知公告ID
            raise_not_exists_exception (bool, optional): 没找到的话是否直接报错
            raise_disable_exception (bool, optional): 禁用的话是否直接报错
            raise_expire_exception (bool, optional): 过期的话是否直接报错

        Returns:
            NoticeCompleteOutSchema | None: 通知公告实例
        """
        search_sql = select(
            *NoticeCompleteOutSchema.convert_to_sql_model_field(Notice)
        ).where(Notice.id == notice_id, Notice.tenant_id == tenant_id)
        async with db_session.begin():
            search_res = await db_session.execute(search_sql)
        result = search_res.mappings().first()
        if result is not None:
            result = NoticeCompleteOutSchema.model_validate(result)
            if (not result.status) and raise_disabled_exception:
                # 禁用则报错
                raise generate_async_validator_exception("id", "通知公告已被禁用")
            if result.expiration_datetime < datetime.now() and raise_expired_exception:
                # 非初始通知公告, 过期则报错
                raise generate_async_validator_exception("id", "通知公告已过期")
            return result
        elif result is None and raise_not_exists_exception:
            # 未找到则报错
            raise generate_async_validator_exception("id", "通知公告不存在")
        else:
            return result

    @data_cache(
        ttl="30m",
        key="no_content_notice_list:{tenant_id}:{search_schema}:{pagination_schema}:{order_schema}",
        tags=["no_content_notice_list:{tenant_id}"],
        condition=CashewsCondition.condition_only_true_result,
    )
    @staticmethod
    async def list_search_no_content_notice(
        db_session: AsyncSession,
        tenant_id: int,
        search_schema: NoticeListSearchValidateSchema,
        pagination_schema: PaginationSearchValidateSchema,
        order_schema: OrderBySearchValidateSchema,
        need_select_total: bool = True,
    ) -> tuple[list[NoticeNoContentOutSchema], int | None]:
        """无公告内容的通知公告列表查询

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id: 租户ID
            search_schema (NoticeListSearchValidateSchema): 查询参数
            pagination_schema: 分页参数
            order_schema: 排序参数
            need_select_total: 是否需要查询总数

        Returns:
            tuple[list[NoticeNoContentOutSchema], int | None]: 查询结果、查询总数
        """
        # 构建排序字段
        order_by_field_sql = order_schema.convert_to_order_by_sql(search_schema, Notice)
        # 构建查询sql
        list_search_sql = (
            select(*NoticeNoContentOutSchema.convert_to_sql_model_field(Notice))
            .where(
                and_(*search_schema.convert_to_where_sql(Notice)),
                Notice.tenant_id == tenant_id,
            )
            .order_by(order_by_field_sql)
            .offset(pagination_schema.offset)
            .limit(pagination_schema.limit)
        )
        # 开始查询
        async with db_session.begin():
            list_search_res = await db_session.execute(list_search_sql)
        result = list_search_res.mappings().all()
        # 根据need_select_total参数来确定是否需要查询总数
        if need_select_total:
            total_sql = (
                select(func.count())
                .select_from(Notice)
                .where(
                    and_(*search_schema.convert_to_where_sql(Notice)),
                    Notice.tenant_id == tenant_id,
                )
            )
            async with db_session.begin():
                total_res = await db_session.execute(total_sql)
            total = total_res.scalar()
        else:
            total = None
        # 统一转为pydantic列表模型
        return ListNoticeNoContentOutSchema.validate_python(result), total

    @data_cache(
        ttl="30m",
        key="forgeground_notice_list:{tenant_id}",
        tags=["forgeground_notice_list:{tenant_id}"],
        condition=CashewsCondition.condition_only_true_result,
    )
    @staticmethod
    async def list_get_forgeground_notice(
        db_session: AsyncSession, tenant_id: int
    ) -> list[NoticeCompleteOutSchema]:
        """获取前台通知公告列表

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id: 租户ID

        Returns:
            list[NoticeCompleteOutSchema]: 查询结果、查询总数
        """
        # 构建查询sql
        list_search_sql = (
            select(*NoticeNoContentOutSchema.convert_to_sql_model_field(Notice))
            .where(
                Notice.notice_type.in_(
                    [
                        NoticeTypeEnum.foreground_announcement.value,
                        NoticeTypeEnum.foreground_notice.value,
                    ]
                ),
                Notice.status.is_(True),
                Notice.expiration_datetime > datetime.now(),
                Notice.tenant_id == tenant_id,
            )
            .order_by(Notice.show_order, Notice.create_datetime)
        )
        # 开始查询
        async with db_session.begin():
            list_search_res = await db_session.execute(list_search_sql)
        result = list_search_res.mappings().all()
        # 统一转为pydantic列表模型
        return ListNoticeCompleteOutSchema.validate_python(result)

    @data_cache(
        ttl="30m",
        key="background_notice_list:{tenant_id}",
        tags=["background_notice_list:{tenant_id}"],
        condition=CashewsCondition.condition_only_true_result,
    )
    @staticmethod
    async def list_get_background_notice(
        db_session: AsyncSession, tenant_id: int
    ) -> list[NoticeCompleteOutSchema]:
        """获取前台通知公告列表

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id: 租户ID

        Returns:
            list[NoticeCompleteOutSchema]: 查询结果、查询总数
        """
        # 构建查询sql
        list_search_sql = (
            select(*NoticeNoContentOutSchema.convert_to_sql_model_field(Notice))
            .where(
                Notice.notice_type.in_(
                    [
                        NoticeTypeEnum.background_announcement.value,
                        NoticeTypeEnum.background_notice.value,
                    ]
                ),
                Notice.status.is_(True),
                Notice.expiration_datetime > datetime.now(),
                Notice.tenant_id == tenant_id,
            )
            .order_by(Notice.show_order, Notice.create_datetime)
        )
        # 开始查询
        async with db_session.begin():
            list_search_res = await db_session.execute(list_search_sql)
        result = list_search_res.mappings().all()
        # 统一转为pydantic列表模型
        return ListNoticeCompleteOutSchema.validate_python(result)

    @staticmethod
    async def create_notice(
        db_session: AsyncSession,
        tenant_id: int,
        create_schema: NoticeCreateValidateSchema,
        creator_username: str = "",
    ) -> int:
        """创建通知公告

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id (int): 租户ID
            create_schema (NoticeCreateValidateSchema): 创建所需的信息
            creator_username (str, optional): 创建者用户名

        Returns:
            int: 创建的sql的主键ID
        """
        # 构建创建信息字典
        create_info_dict = create_schema.model_dump()
        # 创建
        create_sql = insert(Notice).values(
            tenant_id=tenant_id,
            creator_username=creator_username,
            **create_info_dict,
        )
        async with db_session.begin():
            create_res = await db_session.execute(create_sql)
            # 清空相关缓存标签
            await data_cache.delete_tags(
                f"no_content_notice_list:{tenant_id}",
                f"forgeground_notice_list:{tenant_id}",
                f"background_notice_list:{tenant_id}",
            )
        return create_res.inserted_primary_key_rows[0][0]

    @staticmethod
    @data_cache.transaction()
    async def update_notice(
        db_session: AsyncSession,
        tenant_id: int,
        update_schema: NoticeUpdateValidateSchema,
        updater_username: str = "",
    ):
        """修改通知公告信息

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id (int): 租户ID
            update_schema (NoticeUpdateValidateSchema): 修改操作所需的数据
            updater_username (str, optional):修改者用户名
        """
        # 检查需修改的sql实例是否存在(禁用不报错)
        await NoticeService.get_notice_use_id(
            db_session,
            tenant_id,
            update_schema.id,
            raise_disabled_exception=False,
            raise_expired_exception=False,
        )
        # 构建修改所需的数据(排除未设置的值和id)
        # 只允许修改初始通知公告的"访问令牌过期秒数"、"刷新令牌过期秒数"、"授权码过期秒数"
        update_schema_data = update_schema.model_dump(
            exclude=("id",), exclude_unset=True
        )
        if update_schema_data:
            # 修改sql
            update_sql = (
                update(Notice)
                .values(**update_schema_data, updater_username=updater_username)
                .where(
                    Notice.id == update_schema.id,
                    Notice.tenant_id == tenant_id,
                )
            )
            # 修改操作
            async with db_session.begin():
                # 修改sql数据
                await db_session.execute(update_sql)
                # 清空相关缓存标签
                await data_cache.delete_tags(
                    f"notice#id:{tenant_id}:{update_schema.id}",
                    f"no_content_notice_list:{tenant_id}",
                    f"forgeground_notice_list:{tenant_id}",
                    f"background_notice_list:{tenant_id}",
                )

    @staticmethod
    @data_cache.transaction()
    async def update_notice_status(
        db_session: AsyncSession,
        tenant_id: int,
        update_schema: NoticeUpdateStatusValidateSchema,
        updater_username: str = "",
    ):
        """单独修改通知公告状态

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id (int): 租户ID
            update_schema (NoticeUpdateStatusValidateSchema): 修改操作所需的数据
            updater_username (str, optional):修改者用户名
        """
        # 检查需修改的sql实例是否存在(禁用不报错)
        await NoticeService.get_notice_use_id(
            db_session,
            tenant_id,
            update_schema.id,
            raise_disabled_exception=False,
            raise_expired_exception=False,
        )
        # 修改sql
        update_sql = (
            update(Notice)
            .values(status=update_schema.status, updater_username=updater_username)
            .where(Notice.id == update_schema.id, Notice.tenant_id == tenant_id)
        )
        # 修改操作
        async with db_session.begin():
            # 修改sql数据
            await db_session.execute(update_sql)
            # 清空相关缓存标签
            await data_cache.delete_tags(
                f"notice#id:{tenant_id}:{update_schema.id}",
                f"no_content_notice_list:{tenant_id}",
                f"forgeground_notice_list:{tenant_id}",
                f"background_notice_list:{tenant_id}",
            )

    @staticmethod
    @data_cache.transaction()
    async def delete_notice(
        db_session: AsyncSession,
        tenant_id: int,
        delete_schema: NoticeDeleteValidateSchema,
    ):
        """删除通知公告

        Args:
            db_session (AsyncSession): 数据库连接池
            tenant_id: 租户ID
            delete_schema (NoticeDeleteValidateSchema): 删除操作所属的数据
        """
        # 删除通知公告本身的sql
        delete_sql = delete(Notice).where(
            Notice.id.in_(delete_schema.ids), Notice.tenant_id == tenant_id
        )
        # 删除操作
        async with db_session.begin():
            # 删除sql中的数据
            await db_session.execute(delete_sql)
            delete_cache_tags = [
                *[f"notice#id:{tenant_id}:{i}" for i in delete_schema.ids],
                f"no_content_notice_list:{tenant_id}",
                f"forgeground_notice_list:{tenant_id}",
                f"background_notice_list:{tenant_id}",
            ]
            # 清空相关缓存标签
            await data_cache.delete_tags(*delete_cache_tags)
